package com.javabi.sizeof.definition;

import com.javabi.sizeof.ClassDefinition;
import com.javabi.sizeof.ClassDefinitionMap;
import com.javabi.sizeof.MemoryUtil;

import java.lang.reflect.Field;
import java.util.LinkedList;
import java.util.Set;

/**
 * A Linked List Definition (Safe).
 * <p>
 * This definition is 'safe' because it adds the Entry objects to the instance
 * list, unlike the {@link} version which takes
 * approximately half as long to calculate sizes. It also provides a minor
 * performance enhancement on the default calculation strategy.
 */
@SuppressWarnings("unchecked")
public class LinkedListDefinition extends ClassDefinition<LinkedList> {

	/** The LinkedList.header field. */
	private final Field headerField;
	/** The LinkedList$Entry class. */
	private final Class entryClass;
	/** The Entry.element field. */
	private final Field elementField;
	/** The Entry.next field. */
	private final Field nextField;

	/**
	 * Creates a linked list definition.
	 */
	public LinkedListDefinition() throws ClassNotFoundException, NoSuchFieldException {
		headerField = LinkedList.class.getDeclaredField("header");
		headerField.setAccessible(true);
		entryClass = Class.forName("java.util.LinkedList$Entry");
		elementField = entryClass.getDeclaredField("element");
		elementField.setAccessible(true);
		nextField = entryClass.getDeclaredField("next");
		nextField.setAccessible(true);
	}

	@Override
	public long sizeOf(LinkedList list) {
		return 44 + ((long) list.size() * 24);
	}

	@Override
	public final boolean hasElements(LinkedList list) {
		return true;
	}

	@Override
	public long sizeOfElements(LinkedList list, ClassDefinitionMap definitionMap, Set<Object> instanceSet, long size) {
		int listSize = list.size();

		// Iterate over entries
        Object link = null;
        try {
            link = headerField.get(list);
        } catch (IllegalAccessException e) {

        }
        instanceSet.add(link);
		for (int i = 0; i < listSize; i++) {
            try {
                link = nextField.get(link);
            } catch (IllegalAccessException e) {

            }
            instanceSet.add(link);
            Object element = null;
            try {
                element = elementField.get(link);
            } catch (IllegalAccessException e) {

            }
            if (element != null) {
				size += MemoryUtil.sizeOf(element, definitionMap, instanceSet);
			}
		}
		return size;
	}

}
